package com.stylefeng.guns.core.http;

import com.stylefeng.guns.core.exception.GunsException;
import com.stylefeng.guns.core.support.CollectionKit;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.nio.reactor.ConnectingIOReactor;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Created with IntelliJ IDEA.
 *
 * @author: wangxindong
 * @date: 2018/4/21-18:27
 */
public class GunsHttpClientImpl implements GunsHttpClient {


    // ================================================================
    // Constants
    // ================================================================

    private static final Logger LOGGER = LoggerFactory.getLogger(GunsHttpClientImpl.class);

    // ================================================================
    // Fields
    // ================================================================

    /**
     * 单位ms 从服务器读取数据的timeout
     *
     * @Date: 2017年8月31日下午1:20:20
     */
    private final static int SOCKET_TIMEOUT = 30000;
    /**
     * 单位ms 和服务器建立连接的timeout
     *
     * @Date: 2017年8月31日下午1:20:37
     */
    private final static int CONNECTION_TIMEOUT = 30000;
    /**
     * 从连接池获取连接的timeout 单位ms
     *
     * @Date: 2017年8月31日下午1:20:50
     */
    private final static int CONNECTION_REQUEST_TIMEOUT = 3000;

    /**
     * 请求配置
     *
     * @Date: 2017年8月31日下午1:21:09
     */
    private final RequestConfig requestConfig = RequestConfig.custom()
            .setSocketTimeout(SOCKET_TIMEOUT).setConnectTimeout(CONNECTION_TIMEOUT)
            .setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT).build();

    /**
     * http调用客户端
     *
     * @Date: 2017年8月31日下午1:21:23
     */
    private CloseableHttpClient httpclient;

    /**
     * Https请求客户端
     *
     * @Date: 2017年8月31日下午1:21:33
     */
    private CloseableHttpClient httpsclient;

    /**
     * 线程池访问HTTP
     */
    private final int threadTotal = 50;
    private final int threadPerRoute = 20;
    private final int keepAliveMillis = 1000;
    private PoolingHttpClientConnectionManager cm = null;
    private ConnectingIOReactor ioReactor;


    // ================================================================
    // Constructors
    // ================================================================

    // ================================================================
    // Methods from/for super Interfaces or SuperClass
    // ================================================================

    @Override
    public void init() {

    }

    @Override
    public GunsResponseVO sendHttpPost(String httpUrl, String request) throws GunsException {
        return sendHttpPost(httpUrl, null, request, null, false);
    }

    @Override
    public GunsResponseVO sendHttpPost(String httpUrl, String request, boolean isCallHttps) throws GunsException {
        return sendHttpPost(httpUrl, null, request, null, isCallHttps);
    }

    @Override
    public GunsResponseVO sendHttpPost(String httpUrl, Map<String, String> headerInfoMap, String request, String charset, boolean isCallHttps) throws GunsException {
        try {
            final HttpPost httpPost = new HttpPost(httpUrl);

            if (StringUtils.isNotBlank(request)) {
                // 设置参数
                StringEntity stringEntity;
                if (StringUtils.isNotBlank(charset)) {
                    stringEntity = new StringEntity(request, charset);
                } else {
                    stringEntity = new StringEntity(request);
                }

                httpPost.setEntity(stringEntity);
            }

            // 初始化request header
            initRequest(httpPost, headerInfoMap);
            // 打印请求日志
            printRequestInfo(httpPost, charset);
            return sendHttpPost(httpPost, isCallHttps);
        } catch (final UnsupportedEncodingException ex) {
            LOGGER.error("HttpPost Construct UrlEncodedFormEntity Fail.");
            throw new RuntimeException("Janus.HttpAsyncUtil.UnsupportedEncoding");
        } catch (IOException e) {
            throw new RuntimeException("Janus.HttpAsyncUtil.UnsupportedEncoding");
        }
    }

    @Override
    public GunsResponseVO sendHttpPost(String httpUrl, Map<String, String> paramsMap) throws GunsException {
        return sendHttpPost(httpUrl, null, paramsMap, null, false);
    }

    @Override
    public GunsResponseVO sendHttpPost(String httpUrl, Map<String, String> paramsMap, boolean isCallHttps) throws GunsException {
        return sendHttpPost(httpUrl, null, paramsMap, null, isCallHttps);
    }

    @Override
    public GunsResponseVO sendHttpPost(String httpUrl, Map<String, String> headerInfoMap, Map<String, String> paramsMap, String charset, boolean isCallHttps) throws GunsException {
        try {
            final HttpPost httpPost = new HttpPost(httpUrl);

            // 创建参数队列
            if (!CollectionKit.isNotEmpty(paramsMap)) {
                final List<NameValuePair> nameValuePairs = getParams(paramsMap);
                if (StringUtils.isNotBlank(charset)) {
                    httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, charset));
                } else {
                    httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
                }
            }
            // 初始化request header
            initRequest(httpPost, headerInfoMap);
            // 打印请求日志
            printRequestInfo(httpPost, charset);
            return sendHttpPost(httpPost, isCallHttps);
        } catch (final UnsupportedEncodingException ex) {
            LOGGER.error("HttpPost Construct UrlEncodedFormEntity Fail.");
            throw new RuntimeException("Janus.HttpAsyncUtil.UnsupportedEncoding");
        } catch (final IOException e) {
            throw new RuntimeException("Janus.HttpAsyncUtil.UnsupportedEncoding");
        }
    }

    @Override
    public GunsResponseVO sendHttpGet(String url, Map<String, String> paramsMap) throws GunsException {
        return sendHttpGet(url, null, paramsMap, null, false);
    }

    @Override
    public GunsResponseVO sendHttpGet(String url, Map<String, String> paramsMap, boolean isCallHttps) throws GunsException {
        return sendHttpGet(url, null, paramsMap, null, isCallHttps);
    }

    @Override
    public GunsResponseVO sendHttpGet(String httpUrl, Map<String, String> headerInfoMap, Map<String, String> paramsMap, String charset, boolean isCallHttps) throws GunsException {
        try {
            final URIBuilder uriBuilder = new URIBuilder(httpUrl);

            if (StringUtils.isNotBlank(charset)) {
                uriBuilder.setCharset(Charset.forName(charset));
            }

            if (paramsMap != null && !paramsMap.isEmpty()) {
                for (final Map.Entry<String, String> entry : paramsMap.entrySet()) {
                    uriBuilder.addParameter(entry.getKey(), entry.getValue());
                }
            }

            final HttpGet httpGet = new HttpGet(uriBuilder.build());

            // 初始化request header
            initRequest(httpGet, headerInfoMap);
            // 打印请求日志
            printRequestInfo(httpGet, charset);

            return sendHttpGet(httpGet, isCallHttps);
        } catch (final URISyntaxException ex) {
            LOGGER.error("Construct URIBuilder Error. URL=" + httpUrl);
            throw new RuntimeException("Janus.HttpAsyncUtil.ConstructURIBuilder.Error");
        } catch (IOException e) {
            throw new RuntimeException("Janus.HttpAsyncUtil.ConstructURIBuilder.Error");
        }
    }

    // ================================================================
    // Public or Protected Methods
    // ================================================================


    // ================================================================
    // Getter & Setter
    // ================================================================

    // ================================================================
    // Private Methods
    // ================================================================

    public void close() {
        try {
            if (httpclient != null) {
                httpclient.close();
            }

            if (httpsclient != null) {
                httpsclient.close();
            }
        } catch (final Exception e) {
            e.printStackTrace();
        }
    }

    private void httpsStart() {
        if (httpsclient == null) {
            try {
                final SSLContext sslContext =
                        new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                            @Override
                            public boolean isTrusted(
                                    final java.security.cert.X509Certificate[] x509Certificates,
                                    final String s) throws java.security.cert.CertificateException {
                                return true;
                            }
                        }).build();
                httpsclient = HttpClients.custom().setSSLContext(sslContext).build();
            } catch (final Exception e) {
            }
        }
    }

    private CloseableHttpClient getClient() {
        return getClientByPool();
    }

    private GunsResponseVO sendHttpPost(final HttpPost httpPost, final boolean isCallHttps) throws IOException {

        httpPost.setConfig(requestConfig);

        if (isCallHttps) {
            httpsStart();
            HttpResponse response = httpsclient.execute(httpPost);
            return inputToString(response);
        } else {
            HttpResponse response = getClient().execute(httpPost);
            return inputToString(response);
        }
    }

    private GunsResponseVO sendHttpGet(final HttpGet httpGet, final boolean isCallHttps) throws IOException {
        httpGet.setConfig(requestConfig);

        if (isCallHttps) {
            httpsStart();
            HttpResponse response = httpsclient.execute(httpGet);
            response.getEntity().getContent();
            return inputToString(response);
        } else {
            HttpResponse response = getClient().execute(httpGet);
            return inputToString(response);
        }
    }

    private GunsResponseVO inputToString(HttpResponse response) {
        if (null == response) {
            return null;
        }
        HttpEntity entity = response.getEntity();
        if (null == entity) {
            return null;
        }
        String result = null;
        try {
            InputStream inputStream = entity.getContent();
            result = IOUtils.toString(inputStream, "UTF-8");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new GunsResponseVO(result);
    }

    private static List<NameValuePair> getParams(final Map<String, String> paramsMap) {
        List<NameValuePair> params = null;
        if (CollectionKit.isNotEmpty(paramsMap)) {
            params = new ArrayList<>();
            for (final Map.Entry<String, String> entry : paramsMap.entrySet()) {
                params.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
        }

        return params;
    }

    /**
     * 初始化request信息（添加header信息、代理信息）
     *
     * @param httpRequest
     * @param headerInfoMap
     */
    private static void initRequest(final HttpRequestBase httpRequest,
                                    final Map<String, String> headerInfoMap) {
        // 初始化header信息
        if (CollectionKit.isNotEmpty(headerInfoMap)) {
            for (final Map.Entry<String, String> entry : headerInfoMap.entrySet()) {
                httpRequest.addHeader(entry.getKey(), entry.getValue());
            }
        }

        // //是否使用代理
        // if (useProxy) {
        // HttpHost proxy = new HttpHost(Proxy_HostName, Proxy_Port, "http");
        // RequestConfig config =
        // RequestConfig.custom().setProxy(proxy).build();
        // httpRequest.setConfig(config);
        // }
    }

    /**
     * 打印Request信息
     *
     * @param httpRequest
     * @param charset
     * @throws Exception
     */
    private static void printRequestInfo(final HttpRequestBase httpRequest, final String charset) {
        LOGGER.info("Request URI:");
        LOGGER.info(httpRequest.getRequestLine().toString());

        if (CollectionKit.isNotEmpty(httpRequest.getAllHeaders())) {
            LOGGER.info("Request Header:");
            for (int i = 0; i < httpRequest.getAllHeaders().length; i++) {
                final Header header = httpRequest.getAllHeaders()[i];
                LOGGER.info(header.toString());
            }
        }

        if (httpRequest instanceof HttpPost) {
            LOGGER.info("Request Body:");
            final HttpPost httpPost = (HttpPost) httpRequest;

            try {
                if (StringUtils.isNotBlank(charset)) {
                    LOGGER.info(IOUtils.toString(httpPost.getEntity().getContent(),
                            Charset.forName(charset)));
                } else {
                    LOGGER.info(IOUtils.toString(httpPost.getEntity().getContent()));
                }
            } catch (final IOException ex) {
                LOGGER.error("Get Request Body Error.");
            }
        }
    }

    /**
     * 打印response信息
     *
     * @param httpResponse
     * @return
     * @throws Exception
     */
    public static String printResponseInfo(final HttpResponse httpResponse, final String charset)
            throws GunsException {
        LOGGER.info("Response Status:");
        LOGGER.info(httpResponse.getStatusLine().toString());

        LOGGER.info("Response Headers:");
        for (int i = 0; i < httpResponse.getAllHeaders().length; i++) {
            final Header header = httpResponse.getAllHeaders()[i];
            LOGGER.info(header.toString());
        }

        LOGGER.info("Response Body:");
        String rs = "";
        try {
            if (StringUtils.isNotBlank(charset)) {
                rs = EntityUtils.toString(httpResponse.getEntity(), Charset.forName(charset));
            } else {
                rs = EntityUtils.toString(httpResponse.getEntity());
            }
            LOGGER.info(rs);

            return rs;
        } catch (final Exception ex) {
            LOGGER.error("Get Response Body Error.");
            throw new RuntimeException("Janus.HttpAsyncUtil.GetResponse.Error");
        }
    }

    /**
     * 关于方法的描述：通过线程池创建CloseableHttpClient
     *
     * @return CloseableHttpAsyncClient 返回对象
     * @Date:2017年8月31日下午1:27:06
     */
    public CloseableHttpClient getClientByPool() {
        if (httpclient != null) {
            return httpclient;
        }
        try {
            if (ioReactor == null) {
                final IOReactorConfig ioReactorConfig = IOReactorConfig.custom().build();
                ioReactor = new DefaultConnectingIOReactor(ioReactorConfig);
            }
            if (cm == null) {
                cm = new PoolingHttpClientConnectionManager();
                cm.setMaxTotal(threadTotal);
                cm.setDefaultMaxPerRoute(threadPerRoute);
            }
            httpclient = HttpClients.custom().setConnectionManager(cm).build();
        } catch (final Exception e) {
            e.printStackTrace();
        }
        return httpclient;
    }


    // ================================================================
    // Inner or Anonymous Class
    // ================================================================

    // ================================================================
    // Test Methods
    // ================================================================

}
